﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Hexagonal;
using System.Xml;
using System.Threading;
using System.Windows.Forms;
using GeoSwarm_Editor.Game;

namespace GeoSwarm_Editor
{
    /*
     * Stores all the information required to build the final game level
     */
    public class GameLevel
    {

        #region Instance Variables & Constructor

        /*
         * Data used in the level
         */
        private string levelName;
        private string levelDescrption;
        private string levelHint1;
        private string levelHint2;
        private int startingCash;
        private int startingLives;
        private Hex[,] hexGrid;
        private int[] creeps;
        private int[,] creepData;
        private int[] towers;
        private int[] towerData;
        private Wave[] waves;
        private decimal waveDelay;
        private decimal spawnDelay;
        private bool isEndless;
        private decimal speedFactor;
        private decimal healthFactor;
        private decimal wealthFactor;
        private Hex[] exits;
        private Hex[] spawns;
        private Board board;
        private string STMB_title;
        private string STMB_message;

        /*
         * Data used for level creation/opening
         */
        private XmlDocument xmlDoc;
        private XmlElement root;
        private string openFile = string.Empty;
        private bool flag_towers = false;

        /*
         * Constructor - initializes the instance variables used for level creation
         *               and starts the XML document
         */
        public GameLevel(Board board)
        {
            this.board = board;
            this.hexGrid = board.Hexes;
            xmlDoc = new XmlDocument();
            XmlDeclaration dec = xmlDoc.CreateXmlDeclaration("1.0", null, null);
            xmlDoc.AppendChild(dec);
            XmlComment comment = xmlDoc.CreateComment(" This is a game level for geoDefense Swarm. It was generated for personal, non-commercial use by GeoSwarm Editor. Please do not remove this notice. ");
            root = xmlDoc.CreateElement("gameLevel");
            root.SetAttribute("gse", "true");
            xmlDoc.AppendChild(root);
            xmlDoc.InsertBefore(comment, root);
        }

        #endregion
        
        #region Properties

        /*
         * Getters and setters (properties) for all data used in the level
         */

        public string LevelName
        {
            get { return levelName; }
            set { levelName = value; }
        }

        public string LevelDescrption
        {
            get { return levelDescrption; }
            set { levelDescrption = value; }
        }

        public string LevelHint1
        {
            get { return levelHint1; }
            set { levelHint1 = value; }
        }

        public string LevelHint2
        {
            get { return levelHint2; }
            set { levelHint2 = value; }
        }

        public int StartingCash
        {
            get { return startingCash; }
            set { startingCash = value; }
        }

        public int StartingLives
        {
            get { return startingLives; }
            set { startingLives = value; }
        }

        public Hex[,] HexGrid
        {
            get { return hexGrid; }
            set { hexGrid = value; }
        }

        public int[] Creeps
        {
            get { return creeps; }
            set { creeps = value; }
        }

        public int[,] CreepData
        {
            get { return creepData; }
            set { creepData = value; }
        }

        public int[] Towers
        {
            get { return towers; }
            set { towers = value; }
        }

        public int[] TowerData
        {
            get { return towerData; }
            set { towerData = value; }
        }

        public Wave[] Waves
        {
            get { return waves; }
            set { waves = value; }
        }

        public decimal WaveDelay
        {
            get { return waveDelay; }
            set { waveDelay = value; }
        }

        public decimal SpawnDelay
        {
            get { return spawnDelay; }
            set { spawnDelay = value; }
        }

        public Hex[] Spawns
        {
            get { return spawns; }
            set { spawns = value; }
        }

        public Hex[] Exits
        {
            get { return exits; }
            set { exits = value; }
        }

        public Board Board
        {
            get { return board; }
            set { board = value; }
        }

        public bool IsEndless
        {
            get { return isEndless; }
            set { isEndless = value; }
        }

        public decimal WealthFactor
        {
            get { return wealthFactor; }
            set { wealthFactor = value; }
        }

        public decimal SpeedFactor
        {
            get { return speedFactor; }
            set { speedFactor = value; }
        }

        public decimal HealthFactor
        {
            get { return healthFactor; }
            set { healthFactor = value; }
        }

        public string OpenFile
        {
            get { return openFile; }
            set { openFile = value; }
        }

        #endregion

        #region XML Game File Generation

        /*
         * Builds the level and saves to a file
         */
        public void BuildLevel(string filename)
        {
            CompileLevelXML();
            xmlDoc.Save(filename);
        }

        /*
         * Builds the level and returns the XML as a string
         */
        public string BuildLevel()
        {
            CompileLevelXML();
            return xmlDoc.OuterXml;
        }

        /*
         * Builds the level XML
         */
        private void CompileLevelXML()
        {
            XmlElement info = xmlDoc.CreateElement("info");
            info.SetAttribute("name", levelName);
            info.SetAttribute("id", "500");
            info.SetAttribute("initCash", startingCash.ToString());
            info.SetAttribute("initLives", startingLives.ToString());
            info.SetAttribute("description", levelDescrption);
            root.AppendChild(info);
            XmlElement hint = xmlDoc.CreateElement("hint");
            hint.SetAttribute("text", levelHint1);
            root.AppendChild(hint);
            hint = xmlDoc.CreateElement("hint");
            hint.SetAttribute("text", levelHint2);
            root.AppendChild(hint);
            XmlElement hexmap = xmlDoc.CreateElement("hexmap");
            CompileAddExits(hexmap);
            CompileAddSpawns(hexmap);
            CompileAddHexes(hexmap);
            root.AppendChild(hexmap);
            XmlElement creeps = xmlDoc.CreateElement("creeps");
            creeps.SetAttribute("waveSpeedFactor", speedFactor.ToString());
            creeps.SetAttribute("waveWealthFactor", wealthFactor.ToString());
            creeps.SetAttribute("waveHealthFactor", healthFactor.ToString());
            CompileAddCreeps(creeps);
            root.AppendChild(creeps);
            XmlElement creepWaves = xmlDoc.CreateElement("creepWaves");
            creepWaves.SetAttribute("delayBetweenWaves", waveDelay.ToString());
            creepWaves.SetAttribute("delayBetweenSpawns", spawnDelay.ToString());
            if (isEndless)
                creepWaves.SetAttribute("endless", "true");
            else
                creepWaves.SetAttribute("endless", "false");
            CompileAddWaves(creepWaves);
            root.AppendChild(creepWaves);
            XmlElement towers = xmlDoc.CreateElement("towers");
            CompileAddTowers(towers);
            root.AppendChild(towers);
        }

        /*
         * Adds towers to level XML
         */
        private void CompileAddTowers(XmlElement towersXML)
        {
            foreach (int towerID in towers)
            {
                string towerType = GetTowerTypeFromID(towerID);
                XmlElement tower = xmlDoc.CreateElement("tower");
                tower.SetAttribute("type", towerType);
                tower.SetAttribute("cost", towerData[towerID].ToString());
                towersXML.AppendChild(tower);
            }
        }

        /*
         * Fetches the tower type from a tower ID
         */
        private string GetTowerTypeFromID(int towerID)
        {
            switch (towerID)
            {
                case 0:
                    return "BLASTER";
                case 1:
                    return "LASER";
                case 2:
                    return "MISSILE";
                case 3:
                    return "SHOCK";
                case 4:
                    return "THUMP";
                case 5:
                    return "POP";
                default:
                    return "BLASTER";
            }
        }

        /*
         * Fetches the tower ID from a tower type
         */
        private int GetTowerIdFromType(string type)
        {
            switch (type)
            {
                case "BLASTER":
                    return 0;
                case "LASER":
                    return 1;
                case "MISSILE":
                    return 2;
                case "SHOCK":
                    return 3;
                case "THUMP":
                    return 4;
                case "POP":
                    return 5;
                default:
                    return 0;
            }
        }

        /*
         * Fetches the creep ID from a creep type
         */
        private int GetCreepIdFromType(string type)
        {
            switch (type)
            {
                case "CHOMPER":
                    return 0;
                case "SPINNER":
                    return 1;
                case "WIGGLE":
                    return 2;
                case "STAR":
                    return 3;
                case "CUBIC":
                    return 4;
                case "PULSAR":
                    return 5;
                case "SWARM":
                    return 6;
                default:
                    return 0;
            }
        }

        /*
         * Fetches the type ID of a hex from the type name
         */
        private int GetTypeFromName(string name)
        {
            switch (name)
            {
                case "blocked":
                    return 1;
                case "fastpass":
                    return 2;
                case "healpass":
                    return 3;
                case "pass":
                    return 4;
                default:
                    return 0;
            }
        }

        /*
         * Adds waves to level XML
         */
        private void CompileAddWaves(XmlElement creepWaves)
        {
            foreach (Wave currentWave in waves)
            {
                XmlElement wave = xmlDoc.CreateElement("wave");
                wave.SetAttribute("spawnHex", "s" + currentWave.SpawnHex.HexState.HexID);
                if (currentWave.Concurrent)
                    wave.SetAttribute("concurrent", "true");
                foreach (Creep currentCreep in currentWave.Creeps)
                {
                    string creepType = Creep.GetCreepTypeFromID(currentCreep.Type);
                    XmlElement spawn = xmlDoc.CreateElement("spawn");
                    spawn.SetAttribute("type", creepType);
                    spawn.SetAttribute("count", currentCreep.Count.ToString());
                    wave.AppendChild(spawn);
                }
                creepWaves.AppendChild(wave);
            }
        }

        /*
         * Adds creeps to level XML
         */
        private void CompileAddCreeps(XmlElement creeps)
        {
            foreach (int creepID in this.creeps)
            {
                string creepType = Creep.GetCreepTypeFromID(creepID);
                XmlElement creep = xmlDoc.CreateElement("creep");
                creep.SetAttribute("type", creepType);
                creep.SetAttribute("speed", creepData[creepID, 0].ToString());
                creep.SetAttribute("health", creepData[creepID, 1].ToString());
                creeps.AppendChild(creep);
            }
        }

        /*
         * Adds exit hexes to level XML
         */
        private void CompileAddExits(XmlElement hexmap)
        {
            foreach (Hex exit in exits)
            {
                XmlElement exithex = xmlDoc.CreateElement("exithex");
                System.Drawing.Point hexLoc = board.FindHexOnBoard(exit);
                if (Hexagonal.Math.IsEven(hexLoc.Y))
                    hexLoc.X -= 1;
                exithex.SetAttribute("name", "e" + exit.HexState.HexID);
                exithex.SetAttribute("hex", hexLoc.Y + "," + hexLoc.X);
                hexmap.AppendChild(exithex);
            }
        }

        /*
         * Adds spawn hexes to level XML
         */
        private void CompileAddSpawns(XmlElement hexmap)
        {
            foreach (Hex spawn in spawns)
            {
                XmlElement spawnhex = xmlDoc.CreateElement("spawnhex");
                System.Drawing.Point hexLoc = board.FindHexOnBoard(spawn);
                if (Hexagonal.Math.IsEven(hexLoc.Y))
                    hexLoc.X -= 1;
                spawnhex.SetAttribute("name", "s" + spawn.HexState.HexID);
                spawnhex.SetAttribute("hex", hexLoc.Y + "," + hexLoc.X);
                spawnhex.SetAttribute("exit", "e" + spawn.HexState.ExitHex.HexState.HexID);
                hexmap.AppendChild(spawnhex);
            }
        }

        /*
         * Adds all other special hexes to the hexmap
         * Loops through all the hexes picking out the special ones
         */
        private void CompileAddHexes(XmlElement hexmap)
        {
            for (int i = 0; i < hexGrid.GetLength(0); i++)
            {
                for (int j = 0; j < hexGrid.GetLength(1); j++)
                {
                    HexState state = hexGrid[i, j].HexState;
                    System.Drawing.Point hexLoc = new System.Drawing.Point();

                    if(state.HexType > 0)
                        hexLoc = board.FindHexOnBoard(hexGrid[i, j]);

                    if (Hexagonal.Math.IsEven(hexLoc.Y))
                        hexLoc.X -= 1;

                    if (state.HexType > 0 && state.HexType != 5 && state.HexType != 6 && state.HexType != 7)
                    {
                        string type;
                        switch (state.HexType)
                        {
                            case 1:
                                type = "blocked";
                                break;
                            case 2:
                                type = "fastpass";
                                break;
                            case 3:
                                type = "healpass";
                                break;
                            case 4:
                                type = "pass";
                                break;
                            default:
                                type = "blocked";
                                break;
                        }
                        XmlElement specialhex = xmlDoc.CreateElement("specialhex");
                        specialhex.SetAttribute("type", type);
                        specialhex.SetAttribute("hex", hexLoc.Y + "," + hexLoc.X);
                        hexmap.AppendChild(specialhex);
                    }
                    else if (state.HexType == 7)
                    {
                        XmlElement placetower = xmlDoc.CreateElement("placetower");
                        placetower.SetAttribute("type", "POP");
                        placetower.SetAttribute("hex", hexLoc.Y + "," + hexLoc.X);
                        hexmap.AppendChild(placetower);
                    }
                }
            }
        }

        /*
         * Opens the specified XML file and loads the level
         */
        public void OpenLevel()
        {
            xmlDoc = new XmlDocument();
            xmlDoc.Load(openFile);
            root = (XmlElement)xmlDoc.GetElementsByTagName("gameLevel")[0];
            /*if (!root.HasAttribute("gse") || (root.HasAttribute("gse") && root.GetAttribute("gse") != "true"))
                throw new Exception("\nThis level file was not created using GeoSwarm Editor. At this time, GeoSwarm Editor only supports opening levels created using it.\n\nThis is due to some currently unsupported features of some of the in-game levels.");*/
            XmlElement info = GetElementByTagName("info", root);
            levelName = info.GetAttribute("name");
            startingCash = int.Parse(info.GetAttribute("initCash"));
            startingLives = int.Parse(info.GetAttribute("initLives"));
            levelDescrption = info.GetAttribute("description");
            XmlNodeList hints = root.GetElementsByTagName("hint");
            if (hints.Count > 0)
            {
                levelHint1 = ((XmlElement)hints[0]).GetAttribute("text");
                if(hints.Count > 1)
                    levelHint2 = ((XmlElement)hints[1]).GetAttribute("text");
            }
            XmlElement hexmap = GetElementByTagName("hexmap", root);
            XmlNodeList exitHexes = hexmap.GetElementsByTagName("exithex");
            XmlNodeList spawnHexes = hexmap.GetElementsByTagName("spawnhex");
            XmlNodeList specialHexes = hexmap.GetElementsByTagName("specialhex");
            XmlNodeList towerHexes = hexmap.GetElementsByTagName("placetower");
            try
            {
                LoadHexInfo(spawnHexes, exitHexes, specialHexes, towerHexes);
                if(flag_towers)
                    ShowThreadMessageBox("Tower Placement", "It appears as though this level contains pre-placed towers other than vortex towers.\n\nUnfortunately, at the moment GeoSwarm Editor only supports pre-placement of vortex towers, so any other pre-placed towers will not be loaded.");
            }
            catch (IndexOutOfRangeException)
            {
                ShowThreadMessageBox("Open Error", "There was a problem loading the hexes from the level file. It's possible that there were spawn or exit hexes specified outside the bounds of the playable area. Unfortunately GeoSwarm Editor doesn't currently support spawns or exits in these locations. Some or all hexes may not have loaded properly.");
            }
            XmlElement creeps = GetElementByTagName("creeps", root);
            speedFactor = decimal.Parse(creeps.GetAttribute("waveSpeedFactor"));
            wealthFactor = decimal.Parse(creeps.GetAttribute("waveWealthFactor"));
            healthFactor = decimal.Parse(creeps.GetAttribute("waveHealthFactor"));
            LoadCreeps(creeps.GetElementsByTagName("creep"));
            LoadTowers(GetElementByTagName("towers", root).GetElementsByTagName("tower"));
            XmlElement creepWaves = GetElementByTagName("creepWaves", root);
            waveDelay = decimal.Parse(creepWaves.GetAttribute("delayBetweenWaves"));
            spawnDelay = decimal.Parse(creepWaves.GetAttribute("delayBetweenSpawns"));
            if(creepWaves.HasAttribute("endless"))
                isEndless = bool.Parse(creepWaves.GetAttribute("endless"));
            else isEndless = false;
            // TODO: Waves
            XmlNodeList waves = creepWaves.GetElementsByTagName("wave");
            LoadWaves(waves);
        }

        /*
         * Loads all the waves
         */
        private void LoadWaves(XmlNodeList waves)
        {
            List<Wave> wavesList = new List<Wave>();
            foreach (XmlElement XMLwave in waves)
            {
                Wave thisWave;
                if (XMLwave.HasAttribute("spawnHex"))
                {
                    int spawnID = int.Parse(XMLwave.GetAttribute("spawnHex").Substring(1));
                    thisWave = new Wave(this.board.FindHexByID(spawnID));
                }
                else if (spawns.Length > 0)
                {
                    thisWave = new Wave(spawns[0]);
                }
                else
                {
                    throw new Exception("No spawn hex could be found for the current wave.");
                }
                
                if (XMLwave.HasAttribute("concurrent"))
                    thisWave.Concurrent = true;

                // Loop the creep spawns
                thisWave.ClearCreeps();
                XmlNodeList XMLcreeps = XMLwave.GetElementsByTagName("spawn");
                foreach(XmlElement XMLcreepSpawn in XMLcreeps)
                {
                    int creepTypeID = GetCreepIdFromType(XMLcreepSpawn.GetAttribute("type"));
                    int creepCount = int.Parse(XMLcreepSpawn.GetAttribute("count"));
                    thisWave.AddCreep(creepTypeID, creepCount);
                }
                
                wavesList.Add(thisWave);
            }
            this.waves = wavesList.ToArray();
        }

        /*
         * Shows a message box using another thread
         */
        private void ShowThreadMessageBox(string title, string message)
        {
            this.STMB_title = title;
            this.STMB_message = message;
            Thread Mbox = new Thread(new ThreadStart(STMB));
            Mbox.Start();
        }

        private void STMB()
        {
            MessageBox.Show(STMB_message, STMB_title);
        }

        /*
         * Loads tower info
         */
        private void LoadTowers(XmlNodeList towersList)
        {
            int i = 0;
            int[] towers = new int[6];
            int[] towerData = new int[6];
            foreach (XmlNode node in towersList)
            {
                XmlElement elmnt = (XmlElement)node;
                int typeID = GetTowerIdFromType(elmnt.GetAttribute("type"));
                int cost = int.Parse(elmnt.GetAttribute("cost"));

                towers[i] = typeID;
                towerData[typeID] = cost;
                i++;
            }
            this.towers = towers;
            this.towerData = towerData;
        }

        /*
         * Loads creep info
         */
        private void LoadCreeps(XmlNodeList creepsList)
        {
            int i = 0;
            int[] creeps = new int[creepsList.Count];
            int[,] creepData = new int[7, 2];
            foreach (XmlNode node in creepsList)
            {
                XmlElement elmnt = (XmlElement)node;
                int typeID = GetCreepIdFromType(elmnt.GetAttribute("type"));
                int speed = int.Parse(elmnt.GetAttribute("speed"));
                int health = int.Parse(elmnt.GetAttribute("health"));

                creeps[i] = typeID;
                creepData[typeID, 0] = speed;
                creepData[typeID, 1] = health;
                i++;
            }
            this.creeps = creeps;
            this.creepData = creepData;
        }

        /*
         * Loads all the hex info, creates the hex matrix basically
         */
        private void LoadHexInfo(XmlNodeList spawnHexes, XmlNodeList exitHexes, XmlNodeList specialHexes, XmlNodeList towerHexes)
        {
            // Spawns and exits counters
            int s = 0, e = 0;

            // Spawn and exit arrays
            exits = new Hex[exitHexes.Count];
            spawns = new Hex[spawnHexes.Count];

            // Loop the exits
            foreach (XmlNode node in exitHexes)
            {
                XmlElement elmnt = (XmlElement)node;

                string name = elmnt.GetAttribute("name");
                int id = int.Parse(name.Substring(1));

                // Hex is in Y, X format
                // We minus 1 so we can use these in the matrix
                string hex = elmnt.GetAttribute("hex");
                int[] hexArr = IntCSV_Array(hex);
                int Y = hexArr[0];
                int X = hexArr[1];

                if (Hexagonal.Math.IsEven(Y))
                    X += 1;

                hexGrid[X, Y].HexState.HexID = id;
                hexGrid[X, Y].HexState.HexType = 6;

                exits[e] = hexGrid[X, Y];
                e++;
            }

            // Loop the spawns
            foreach (XmlNode node in spawnHexes)
            {
                XmlElement elmnt = (XmlElement)node;

                string name = elmnt.GetAttribute("name");
                int id = int.Parse(name.Substring(1));
                string exitName = elmnt.GetAttribute("exit");
                int exitID = int.Parse(exitName.Substring(1));

                // Hex is in Y, X format
                // We minus 1 so we can use these in the matrix
                string hex = elmnt.GetAttribute("hex");
                int[] hexArr = IntCSV_Array(hex);
                int Y = hexArr[0];
                int X = hexArr[1];

                if (Hexagonal.Math.IsEven(Y))
                    X += 1;

                hexGrid[X, Y].HexState.HexID = id;
                hexGrid[X, Y].HexState.ExitHex = FindExitHex(exitID);
                hexGrid[X, Y].HexState.HexType = 5;

                spawns[s] = hexGrid[X, Y];
                s++;
            }

            // Loop the special hexes
            foreach (XmlNode node in specialHexes)
            {
                XmlElement elmnt = (XmlElement)node;

                string type = elmnt.GetAttribute("type");
                int typeID = GetTypeFromName(type);

                // Hex is in Y, X format
                // We minus 1 so we can use these in the matrix
                string hex = elmnt.GetAttribute("hex");
                int[] hexArr = IntCSV_Array(hex);
                int Y = hexArr[0];
                int X = hexArr[1];

                if (Hexagonal.Math.IsEven(Y))
                    X += 1;

                hexGrid[X, Y].HexState.HexType = typeID;
            }

            // Loop the tower spawn hexes
            foreach (XmlNode node in towerHexes)
            {
                XmlElement elmnt = (XmlElement)node;

                // Hex is in Y, X format
                // We minus 1 so we can use these in the matrix
                string hex = elmnt.GetAttribute("hex");
                int[] hexArr = IntCSV_Array(hex);
                int Y = hexArr[0];
                int X = hexArr[1];

                if (Hexagonal.Math.IsEven(Y))
                    X += 1;
                
                // Set hex as POP tower (vortex tower)
                // TODO: If allowing pre-placement of towers, make this changeable
                if (elmnt.HasAttribute("type") && elmnt.GetAttribute("type") == "POP")
                    hexGrid[X, Y].HexState.HexType = 7;
                else
                    flag_towers = true;
            }
        }

        /*
         * Finds an exit hex in the exits list
         */
        private Hex FindExitHex(int exitID)
        {
            foreach (Hex hex in exits)
            {
                if (hex.HexState.HexID == exitID)
                    return hex;
            }
            return null;
        }

        /*
         * Takes some CSV ints and spits out an int array
         */
        private int[] IntCSV_Array(string csv)
        {
            string[] stringArr = csv.Split(new char[] { ',' });
            int[] intArr = new int[stringArr.Length];
            for (int i = 0; i < stringArr.Length; i++)
                intArr[i] = int.Parse(stringArr[i]);
            return intArr;
        }

        /*
         * Grabs an element using just it's tag name
         */
        private XmlElement GetElementByTagName(string tagName, XmlElement parent)
        {
            return (XmlElement) parent.GetElementsByTagName(tagName)[0];
        }

        #endregion

    }
}